home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-12-17 | 16.7 KB | 606 lines | [TEXT/ALFA] |
- ## -*-Tcl-*-
- # ###################################################################
- # Vince's Additions - an extension package for Alpha
- #
- # FILE: "elecTemplates.tcl"
- # created: 24/2/97 {1:34:29 pm}
- # last update: 17/12/97 {4:25:52 pm}
- # Author: Vince Darley
- # E-mail: <darley@fas.harvard.edu>
- # mail: Division of Applied Sciences, Harvard University
- # Oxford Street, Cambridge MA 02138, USA
- # www: <http://www.fas.harvard.edu/~darley/>
- #
- # Routines for electric insertions, and keeping track of template
- # positions.
- # ###################################################################
- ##
-
- alpha::extension betterTemplates 9.0b4 {
- alpha::package require elecBindings 9.0b1
- alpha::useElectricTemplates
- lunion varPrefs(Electrics) [list "Better Templates:" stopNavigationMsgOff \
- templateStopColor maxTemplateNesting \
- TemplatePrompts TemplateWrappers]
- # colour of template stops (magenta default)
- newPref var templateStopColor 4 global "" alpha::basiccolors varindex
- # level of nesting we allow before clearing
- newPref var maxTemplateNesting 5
- ##
- # The format of the template stops:
- # 0 = just use bullets
- # 1 = use bullets but signal the name in the status window
- # 2 = insert names into the window with the bullets
- # 3 = insert names and highlight into the window with the bullets
- ##
- newPref var TemplatePrompts 1 global "" [list {Just use bullets} \
- {Use bullets and status window prompt} {Put prompts in the text} \
- {Highlight prompts in the text}] index
- newPref var TemplateWrappers 0 global ring::_changeTemplateWrappers \
- [list {<Angle brackets>} {“Curly quotes”} {«Curly brackets»} ] index
- newPref flag stopNavigationMsgOff 0
- ring::setTemplateMessage
- # setup template wrappers
- ring::_changeTemplateWrappers
- # call on close to clear the stop ring.
- hook::register closeHook ring::unsetName
- } maintainer {
- "Vince Darley" darley@fas.harvard.edu <http://www.fas.harvard.edu/~darley/>
- } uninstall this-file help {file "ElecCompletions Help"} disable {
- source ${HOME}:Tcl:SystemCode:templates.tcl
- }
-
- # we don't want to be auto-loaded unless we're active.
- if ![package::active betterTemplates] {
- alertnote "Something's trying to auto-load the betterTemplates extension\
- but it's not active!"
- return
- }
-
- # indicates we're a better ring
- proc ring::type {} { return 1 }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "ring::getTMarkPos" --
- #
- # This procedure is pretty slow, due to limitations in Alpha. Hopefully
- # one day we'll have a hard-wired quick version. It also works around
- # a bug in which Alpha tells us that windows which aren't files (e.g.
- # just created with 'new -n') are located in Alpha's 'pwd' directory.
- #
- # The stupid thing is that Alpha knows exactly where each stop is, but
- # it won't tell us! 'gotoTMark $m' goes to exactly the position we
- # want, but we can't find it out some other way.
- # -------------------------------------------------------------------------
- ##
- proc ring::getTMarkPos {m} {
- regexp {(.*) <[0-9]+>$} [set f [win::Current]] dmy f
- if [file exists $f] {
- if [regexp "\{$m \{[quote::Regfind $f]\} (\[0-9\]+) \[0-9\]+\}" [getTMarks] dummy where] {
- return $where
- }
- } else {
- if [regexp "\{$m \{[quote::Regfind [pwd]$f]\} (\[0-9\]+) \[0-9\]+\}" [getTMarks] dummy where] {
- return $where
- }
- }
- error "No such mark"
- }
-
- proc ring::isTMarkAt {p} {
- regexp {(.*) <[0-9]+>$} [set f [win::Current]] dmy f
- if [file exists $f] {
- if [regexp "\{(stop\[0-9\]+:\[0-9\]+) \{[quote::Regfind $f]\} $p $p\}" [getTMarks] dummy which] {
- return $which
- }
- } else {
- if [regexp "\{(stop\[0-9\]+:\[0-9\]+) \{[quote::Regfind [pwd]$f]\} $p $p\}" [getTMarks] dummy which] {
- return $which
- }
- }
- return ""
- }
-
- proc ring::isNested {p} {
- if {![catch {ring::minmax} mm] && $p >= [lindex $mm 0] && $p <= [lindex $mm 1]} {
- return 1
- } else {
- ring::clear
- return 0
- }
- }
-
- proc ring::nestedPos {pos} {
- if [catch {
- if {$pos < [ring::getTMarkPos nestStart]} { return -1 }
- if {$pos > [ring::getTMarkPos nestEnd]} { return -1 }
- }] { return -1 }
- set positions [ring::orderAndPositions max]
- if {$positions == "" || $pos < [lindex $positions 0] || $pos >= $max} {
- return -1
- } else {
- set i 0
- while {$pos >= [lindex $positions $i]} {incr i}
- return $i
- }
- }
-
- proc ring::minmax {} {
- return [list [ring::getTMarkPos nestStart] [ring::getTMarkPos nestEnd]]
- }
- proc ring::list {} {
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing([ring::winName]) s
- if ![info exists s] {
- return [ring::clear]
- }
- set s
- }
-
- proc ring::clear {} {
- set x [ring::winName]
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing($x) s
- if {[info exists s] && $s != ""} {
- ring::_ensure_no_bullets $s
- }
- set s ""
- upvar \#0 __elecRingPrompts$x w
- catch {unset w}
- global __elecNestingLevel __elecLastStop
- set __elecNestingLevel($x) 0
- set __elecLastStop($x) ""
-
- removeTMark "nestStart"
- removeTMark "nestEnd"
- }
-
- proc ring::unsetName {name} {
- ring::unseti [join [file tail $name] ""]
- }
-
- proc ring::unseti {x} {
- global __elecRing __elecNestingLevel __elecLastStop __elecRingPrompts$x
- catch {unset __elecRing($x)}
- catch {unset __elecNestingLevel($x)}
- catch {unset __elecLastStop($x)}
- catch {unset __elecRingPrompts$x}
- }
-
- proc ring::_ensure_no_bullets {stops} {
- message "Deleting non-nested prompts…"
- createTMark "_deleting_" [getPos]
- foreach stop $stops {
- if ![catch {ring::getTMarkPos $stop} p] {
- ring::_deleteBullet $p
- removeTMark $stop
- }
- }
- message ""
- gotoTMark "_deleting_"
- removeTMark "_deleting_"
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "ring::replaceStopMatches" --
- #
- # Replace all stops which match 'stoppat' (a simple glob like pattern)
- # with the text '$text'. The stops are permanently deleted.
- # -------------------------------------------------------------------------
- ##
- proc ring::replaceStopMatches {stoppat text} {
- # get a local reference to the window's stopRing
- set x [ring::winName]
- upvar \#0 __elecRing($x) s
- if [info exists s] {
- pushPosition
- upvar \#0 __elecRingPrompts$x w
- set i 0
- foreach stop $s {
- if [string match $stoppat $w($stop)] {
- if ![catch {ring::getTMarkPos $stop} p] {
- if [ring::_deleteBullet $p] {
- insertText $text
- }
- removeTMark $stop
- set s [lreplace $s $i $i]
- incr i -1
- }
- }
- incr i
- }
- popPosition
- } else {
- ring::clear
- }
- }
-
- proc ring::winName {} { return [join [win::CurrentTail] ""] }
-
- proc ring::order {} {
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing([ring::winName]) s
- if [info exists s] {
- for {set i 0} {$i <100} {incr i} {
- if { [set lpos [lsearch -exact $s stop0:${i}]] != -1 } {
- set s [concat [lrange $s $lpos end] [lrange $s 0 [incr lpos -1]]]
- return $s
- }
- }
- } else {
- ring::clear
- }
- }
-
- proc ring::orderAndPositions {{mx ""}} {
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing([ring::winName]) s
- if {[info exists s] && ([string trim $s] != {}) } {
- if {$mx != ""} { upvar $mx max }
- set max -1
- foreach st $s {
- if {[set p [ring::getTMarkPos $st]] > $max} {
- set max $p
- }
- lappend positions $p
- }
- set lpos [lsearch -exact $positions $max]
- set s [concat [lrange $s [expr $lpos +1] end] [lrange $s 0 $lpos]]
- set positions [concat [lrange $positions [expr $lpos +1] end] \
- [lrange $positions 0 $lpos]]
- return $positions
- } else {
- ring::clear
- return ""
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "ring::_deleteBullet" --
- #
- # Deletes the bullet and a following tag-prompt. The mark moves to the
- # location of the deleted text (side-effect). Returns '1' if the deletion
- # was successful, else '0'.
- # -------------------------------------------------------------------------
- ##
- proc ring::_deleteBullet {p {h 0}} {
- if {[lookAt $p] == "•"} {
- global ring::_tstart ring::_tmatch
- if {[lookAt [expr $p +1]] == ${ring::_tstart} } {
- set ppos [search -s -f 1 -r 1 -l [expr $p + 80] -n ${ring::_tmatch} $p]
- if { [lindex $ppos 0] == $p } {
- if $h {
- eval select $ppos
- } else {
- eval deleteText $ppos
- }
- return 1
- }
- }
- deleteText $p [incr p]
- return 1
- }
- return 0
- }
-
- proc ring::_goto {rest} {
- global __elecLastStop ring::_templateMessage TemplatePrompts
- set x [ring::winName]
- gotoTMark [set __elecLastStop($x) $rest]
- # remove the stop '•' plus optional prompt-tag.
- ring::_deleteBullet [getPos] [expr $TemplatePrompts == 3]
- if $TemplatePrompts {
- upvar \#0 __elecRingPrompts$x w
- if {$w($rest) != ""} {
- message "Fill in '$w($rest)'${ring::_templateMessage}"
- } else {
- message "Fill in template stop${ring::_templateMessage}"
- }
- }
- }
-
- proc ring::nth {} {
- # get a local reference to the window's stopRing
- set x [ring::winName]
- upvar \#0 __elecRing($x) s
- upvar \#0 __elecRingPrompts$x w
- foreach f $s {
- if {$w($f) != ""} {
- lappend l "$f -- $w($f)"
- } else {
- lappend l "$f -- (no prompt)"
- }
- }
- if ![info exists l] { beep; message "No template stops exist." }
- set item [lindex [listpick -p "Pick a stop (listed from current pos)…" $l] 0]
- ring::goto $item
- }
- proc ring::goto {stop} {
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing([ring::winName]) s
- if [info exists s] {
- if { [set lpos [lsearch -exact $s $stop]] != -1 } {
- set s [concat [lrange $s $lpos end] [lrange $s 0 [incr lpos -1]]]
- ring::_goto $stop
- }
- } else {
- ring::clear
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "ring::TMarkAt" --
- #
- # Is the template stop with prompt 'name' at position 'pos'. The 'name'
- # is the name of the enclosed prompt as in '•environment name•', but
- # without the bullets. It is matched via 'string match'.
- # -------------------------------------------------------------------------
- ##
- proc ring::TMarkAt {name pos} {
- set stop [ring::isTMarkAt $pos]
- if {$stop != ""} {
- set x [ring::winName]
- upvar \#0 __elecRingPrompts$x w
- return [string match $name $w($stop)]
- } else {
- return 0
- }
- }
-
- proc ring::+ {} {
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing([ring::winName]) s
- set first [lindex $s 0]
- set s [lreplace $s 0 0]
- lappend s $first
- set next [lindex $s 0]
- ring::_goto $next
- }
- proc ring::- {} {
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing([ring::winName]) s
- set end [expr [llength $s] - 1]
- set last [lindex $s $end]
- set s [lreplace $s $end $end]
- set s [linsert $s 0 $last]
- ring::_goto $last
- }
-
- proc ring::deleteBulletAndMove {} {
- ring::_deleteBullet [getPos]
- ring::+
- }
-
- proc ring::deleteStopAndMove {} {
- ring::_deleteStop
- upvar \#0 __elecRing([ring::winName]) s
- ring::_goto [lindex $s 0]
- }
-
- proc ring::deleteStop {} {
- ring::_deleteStop
- }
-
- proc ring::_deleteStop {} {
- global __elecLastStop
- set x [ring::winName]
- # get a local reference to the window's stopRing
- upvar \#0 __elecRing($x) s
- set l [lsearch -exact $s $__elecLastStop($x)]
- if {$l != -1 } {
- global TemplatePrompts
- if {$TemplatePrompts == 3} {
- ring::_deleteBullet [getPos]
- }
- set s [lreplace $s $l $l]
- removeTMark $__elecLastStop($x)
- set __elecLastStop($x) ""
- }
- }
-
- proc ring::insert {rest {goto 1}} {
- global __elecNestingLevel __elecCurrentNesting maxTemplateNesting
- # get a local reference to the window's stopRing
- set x [ring::winName]
- upvar \#0 __elecRing($x) s
- # if not nested, clear everything
- if {[set p [ring::nestedPos [getPos]]] == "-1" \
- || [incr __elecNestingLevel($x)] > $maxTemplateNesting } {
- ring::clear
- set p 0
- }
- set _level $__elecNestingLevel($x)
- # preliminaries
- set pos [getPos]
- set ii [set i 0]
- upvar \#0 __elecRingPrompts$x w
- global __elecPrompts
- if ![info exists __elecPrompts] {
- set __elecPrompts ""
- }
- # do the stop ring, extracting prompts from '__elecPrompts'
- while {[regexp -indices "•" $rest I] == 1} {
- regsub "•" $rest "o" rest
- createTMark "stop${_level}:$i" [expr $pos + [lindex $I 0]]
- lappend ss "stop${_level}:$i"
- set w(stop${_level}:$i) [lindex $__elecPrompts $i]
- #set __elecPrompts [lrange $__elecPrompts 1 end]
- incr i
- }
- if {$i > 2 || ($i == 2 && $_level == 0)} {
- # store insertion's min and max, if we have more than two stops
- createTMark "nestStart" $pos
- createTMark "nestEnd" [expr $pos + [string length $rest]]
- }
- # put the stop ring together
- set s [concat $ss [lrange $s $p end] [lrange $s 0 [expr $p -1]]]
- # forget the prompt list (we've stored them in an array)
- unset __elecPrompts
- # goto the first stop we just inserted
- if $goto {
- ring::_goto "stop${_level}:${ii}"
- }
- }
-
-
- proc ring::_changeTemplateWrappers {{flag ""}} {
- global flag::list TemplateWrappers
- set wrap [lindex [lindex [set flag::list(TemplateWrappers)] 1] $TemplateWrappers]
- global ring::_tstart ring::_tend ring::_tmatch
- set a [string index $wrap 0]
- set b [string index $wrap [expr [string length $wrap] -1]]
-
- set "ring::_tstart" $a
- set "ring::_tend" $b
- # set "ring::_tmatch" "•${a}\[^${a}${b}\]*${b}"
- set "ring::_tmatch" "(•${a}\[^${a}${b}]*${b}|•${a}(\[^${a}${b}\]*(${a}\[^${a}${b}\]*${b})\[^${a}${b}\]*)*${b})"
- }
-
- proc ring::setTemplateMessage {} {
- global electricBindings ring::_templateMessage stopNavigationMsgOff
- set ring::_templateMessage [lindex \
- {", press (shift)-Tab to move to the next (previous) stop." \
- ", press (shift)-ctrl-j to move to the next (previous) stop." \
- ", press user-defined keys to move from stop to stop." } \
- $electricBindings]
- if {$stopNavigationMsgOff} {
- set ring::_templateMessage ""
- }
- }
-
-
-
- ##
- # -------------------------------------------------------------------------
- #
- # "elec::_Insertion" --
- #
- # Insert a piece of text, padding on the left appropriately. The text
- # should already be correctly indented within itself.
- # -------------------------------------------------------------------------
- ##
- proc elec::_Insertion { center args } {
- global __elecPrompts TemplatePrompts
- set text [join $args ""]
- set pos [getPos]
- regsub -all "\t" $text [text::Tab] text
- if [regexp "\[\n\r\]" $text] {
- regsub -all "\[\n\r\]" $text "\r[text::indentTo $pos]" text
- }
- if [regexp "…" $text] {
- regsub -all "…" $text [text::halfTab] text
- }
- if {![regexp "•" $text] || ([regexp {^([^•]*)••$} $text "" text])} {
- setMark
- insertText $text
- if $center { centerRedraw }
- return
- }
- switch $TemplatePrompts {
- 0 {
- set t $text
- regsub -all {•[^•]*•} $text "•" text
- insertText $text
- while {[regexp {^([^•]*)•([^•]*)•(.*)$} $t dmy tt hyper t]} {
- lappend __elecPrompts $hyper
- }
- }
- 1 {
- while {[regexp {^([^•]*)•([^•]*)•(.*)$} $text dmy tt hyper text]} {
- lappend __elecPrompts $hyper
- append t "${tt}•"
- lappend colours [list [string length $tt] 1]
- }
- append t $text
- }
- 2 -
- 3 {
- global ring::_tstart ring::_tend
- while {[regexp {^([^•]*)•([^•]*)•(.*)$} $text dmy tt hyper text]} {
- lappend __elecPrompts $hyper
- if {$hyper != ""} {
- append t "${tt}•${ring::_tstart}${hyper}${ring::_tend}"
- lappend colours [list [string length $tt] [expr 3 + [string length $hyper]]]
- } else {
- append t "${tt}•"
- lappend colours [list [string length $tt] 1]
- }
- }
- append t $text
- }
- }
- if $TemplatePrompts {
- set p $pos
- # we insert in one chunk so undoing is easy.
- insertText $t
- global templateStopColor
- if {$templateStopColor} {
- foreach col $colours {
- insertColorEscape [incr p [lindex $col 0]] $templateStopColor
- insertColorEscape [incr p [lindex $col 1]] 0
- }
- }
-
- set text $t
- }
-
- goto $pos
- if $center { centerRedraw }
- ring::insert $text
- }
-
-
- # ◊◊◊◊ possible tab key bindings ◊◊◊◊ #
- # note: Also provided by the base Alpha system, these overide when
- # Univs Completions package is in use (these may be more intricate).
-
- ##
- # -------------------------------------------------------------------------
- #
- # "bind::IndentOrNextstop" --
- #
- # Either insert a real tab if your mode hasn't defined its electricTab
- # variable, or jump to the next template stop (if we're mid-template),
- # or indent the current line correctly.
- # -------------------------------------------------------------------------
- ##
- proc bind::IndentOrNextstop {{hard 0}} {
- if {$hard || ![elec::_haveTab] } {
- insertActualTab
- } else {
- global tabNeverIndents
- if {[info exists tabNeverIndents] && $tabNeverIndents} { return [ring::+] }
- if [ring::isNested [getPos]] {
- ring::+
- } else {
- bind::IndentLine
- }
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "bind::TabOrComplete" --
- #
- # Either insert a real tab if your mode hasn't defined its electricTab
- # variable, or invoke the completion mechanism, or indent the current
- # line correctly.
- # -------------------------------------------------------------------------
- ##
- proc bind::TabOrComplete {{hard 0}} {
- if {$hard || ![elec::_haveTab] } {
- insertActualTab
- } else {
- bind::Completion
- }
- }
-